package org.lc.platform.mybatis.manager.relation;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.lc.platform.mybatis.manager.annotation.ManyToMany;
import org.lc.platform.mybatis.manager.annotation.ManyToOne;
import org.lc.platform.mybatis.manager.annotation.OneToMany;
import org.lc.platform.mybatis.manager.annotation.OneToOne;
import org.lc.platform.mybatis.manager.enums.RelationEnum;
import org.lc.platform.mybatis.manager.util.ManagerUtil;

import java.lang.reflect.Field;
import java.util.Objects;

@Data
public class Relation {
    /* 字段关系类型*/
    private RelationEnum relationType = RelationEnum.NO_RELATION;
    /* 对应关联字段*/
    private String selfRelationName;
    /* 对应关联字段数据库名称*/
    private String selfRelationTableName;
    /* 添加注解字段字段*/
    private String selfName;
    /* 对应关联类*/
    private Class<?> relationCls;
    /* 目标关联字段*/
    private String targetRelationName;
    /* 目标关联字段数据库名称*/
    private String targetRelationTableName;
    /* 关联的表*/
    private String relationName;
    /* 关联的表的别名*/
    private String relationAlisaName;
    /*是否是关联字段拥有者*/
    private boolean joinField = false;

    /* 表示每次请求是否自动添加关联关系*/
    boolean eager = false;

    /*中间表的表名*/
    private String middleName;
    /*中间表的别名*/
    private String middleAlisaName;
    /* 对应中间表类*/
    private Class<?> relationMiddleCls;
    /* 中间表当前关联字段*/
    private String selfMiddleName;
    /* 中间表当前关联字段数据库名称*/
    private String selfMiddleTableName;
    /* 中间表目标关联字段*/
    private String targetMiddleName;
    /* 中间表目标关联字段数据库*/
    private String targetMiddleTableName;


    public boolean isHasRelation() {
        return StrUtil.isNotEmpty(selfRelationName) && StrUtil.isNotEmpty(targetRelationName);
    }


    public static Relation of(Field f) {
        Relation rel = new Relation();
        rel.setRelationInfo(f);
        return rel;
    }

    public void setRelationInfo(Field f) {
        this.setOneToOne(f);
    }

    private void setOneToOne(Field f) {
        OneToOne r = f.getAnnotation(OneToOne.class);
        if (r != null) {
            setRelationType(RelationEnum.ONE_TO_ONE);
            setJoinField(r.containId());
            String name = r.selfField();
            String targetName = r.targetField();
            Class<?> clazz = r.target().equals(Object.class) ? ManagerUtil.getClsByField(f) : r.target();
            if (r.containId()) {
                if (StrUtil.isEmpty(targetName)) {
                    targetName = getClsTableId(clazz);
                    setTargetRelationTableName(targetName);
                } else {
                    var tableName = getClsTableName(clazz, targetName);
                    setTargetRelationTableName(tableName);
                }
                var selfTableName = getClsTableName(f.getDeclaringClass(), name);
                setSelfRelationTableName(selfTableName);
            } else {
                if (StrUtil.isEmpty(name)) {
                    name = getClsTableId(f.getDeclaringClass());
                    setSelfRelationTableName(name);
                } else {
                    var selfTableName = getClsTableName(f.getDeclaringClass(), name);
                    setSelfRelationTableName(selfTableName);
                }
                var tableName = getClsTableName(clazz, targetName);
                setTargetRelationTableName(tableName);
            }
            setBaseRelation(f, name, targetName, clazz, r.eager());
        } else {
            setOneToMany(f);
        }
    }

    private void setOneToMany(Field f) {
        OneToMany r = f.getAnnotation(OneToMany.class);
        if (r != null) {
            setRelationType(RelationEnum.ONE_TO_MANY);
            String name = r.selfField();
            var clazz = r.target().equals(Object.class) ? ManagerUtil.getClsByField(f) : r.target();
            if (StrUtil.isEmpty(name)) {
                name = getClsTableId(f.getDeclaringClass());
                setSelfRelationTableName(name);
            } else {
                var selfTableName = getClsTableName(f.getDeclaringClass(), name);
                setSelfRelationTableName(selfTableName);
            }
            String targetName = r.targetField();
            var tableName = getClsTableName(clazz, targetName);
            setTargetRelationTableName(tableName);
            setBaseRelation(f, name, targetName, clazz, r.eager());
        } else {
            setManyToOne(f);
        }
    }

    private void setManyToOne(Field f) {
        ManyToOne r = f.getAnnotation(ManyToOne.class);
        if (r != null) {
            setRelationType(RelationEnum.MANY_TO_ONE);
            String targetName = r.targetField();
            Class<?> clazz = r.target().equals(Object.class) ? ManagerUtil.getClsByField(f) : r.target();
            if (StrUtil.isEmpty(targetName)) {
                targetName = getClsTableId(clazz);
                setTargetRelationTableName(targetName);
            } else {
                var tableName = getClsTableName(clazz, targetName);
                setTargetRelationTableName(tableName);
            }
            String name = r.selfField();
            var selfTableName = getClsTableName(f.getDeclaringClass(), name);
            setSelfRelationTableName(selfTableName);
            setBaseRelation(f, r.selfField(), targetName, clazz, r.eager());
        } else {
            setManyToMany(f);
        }
    }

    private void setManyToMany(Field f) {
        ManyToMany r = f.getAnnotation(ManyToMany.class);
        if (r != null) {
            setRelationType(RelationEnum.MANY_TO_MANY);
            String name = r.selfField();
            String targetName = r.targetField();
            Class<?> clazz = r.target().equals(Object.class) ? ManagerUtil.getClsByField(f) : r.target();
            if (StrUtil.isEmpty(name)) {
                name = getClsTableId(f.getDeclaringClass());
                setSelfRelationTableName(name);
            } else {
                var selfTableName = getClsTableName(f.getDeclaringClass(), name);
                setSelfRelationTableName(selfTableName);
            }
            if (StrUtil.isEmpty(targetName)) {
                targetName = getClsTableId(clazz);
                setTargetRelationTableName(targetName);
            } else {
                var tableName = getClsTableName(clazz, targetName);
                setTargetRelationTableName(tableName);
            }
            setBaseRelation(f, name, targetName, clazz, r.eager());
            /*中间表基础数据*/
            String tableName = StrUtil.toUnderlineCase(r.middleTable().getSimpleName());
            setMiddleName(tableName);
            setRelationMiddleCls(r.middleTable());
            /*中间表当前字段*/
            var tableMiddleSelfName = getClsTableName(r.middleTable(), r.middleSelfField());
            setSelfMiddleTableName(tableMiddleSelfName);
            setSelfMiddleName(r.middleSelfField());
            /*中间表目标字段*/
            var tableMiddleTargetName = getClsTableName(r.middleTable(), r.middleTargetField());
            setTargetMiddleTableName(tableMiddleTargetName);
            setTargetMiddleName(r.middleTargetField());
        }
    }

    private void setBaseRelation(Field f, String selfField, String targetField, Class<?> clazz, boolean eager) {
        /* 当前实体基础数据*/
        setSelfRelationName(selfField);
        setSelfName(f.getName());
        /* 关联目标基础数据*/
        setRelationCls(clazz);
        setEager(eager);
        TableName table = clazz.getAnnotation(TableName.class);
        String tableName = StrUtil.isNotEmpty(table.value()) ? table.value() : StrUtil.toUnderlineCase(clazz.getSimpleName());
        setRelationName(tableName);
        setTargetRelationName(targetField);
    }

    private String getClsTableId(Class<?> cls) {
        Field[] fields = ReflectUtil.getFields(cls);
        for (Field f : fields) {
            var tableId = f.getAnnotation(TableId.class);
            if (tableId != null) {
                return StrUtil.isNotEmpty(tableId.value()) ? tableId.value() : f.getName();
            }
        }
        return null;
    }

    private String getClsTableName(Class<?> cls, String name) {
        Field[] fields = ReflectUtil.getFields(cls);
        for (Field f : fields) {
            var tableName = f.getAnnotation(TableField.class);
            if (f.getName().equals(name)) {
                if (tableName != null && !Objects.equals(tableName.value(), "")) {
                    return tableName.value();
                }
                return StrUtil.toUnderlineCase(f.getName());
            }
        }
        return null;
    }
}
